﻿using System;
using System.Collections;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO.Compression;
using System.Security.Cryptography;

namespace GT
{
    public static partial class Extensions
    {
        #region byte

        public static byte And(this byte x, byte y)
        {
            BitArray bitsX = new BitArray(new byte[] { x });
            BitArray bitsY = new BitArray(new byte[] { y });
            BitArray ret = bitsX.And(bitsY);
            byte[] bytes = new byte[1];
            ret.CopyTo(bytes, 0);
            return bytes[0];
        }

        public static byte Nand(this byte x, byte y)
        {
            return x.And(y.Not());
        }

        public static byte Nor(this byte x, byte y)
        {
            return x.Or(y.Not());
        }

        public static byte Not(this byte x)
        {
            BitArray bits = new BitArray(new byte[] { x });
            BitArray ret = bits.Not();
            byte[] bytes = new byte[1];
            ret.CopyTo(bytes, 0);
            return bytes[0];
        }

        public static byte Or(this byte x, byte y)
        {
            BitArray bitsX = new BitArray(new byte[] { x });
            BitArray bitsY = new BitArray(new byte[] { y });
            BitArray ret = bitsX.Or(bitsY);
            byte[] bytes = new byte[1];
            ret.CopyTo(bytes, 0);
            return bytes[0];
        }

        public static byte Xnor(this byte x, byte y)
        {
            return x.Xor(y.Not());
        }

        public static byte Xor(this byte x, byte y)
        {
            BitArray bitsX = new BitArray(new byte[] { x });
            BitArray bitsY = new BitArray(new byte[] { y });
            BitArray ret = bitsX.Xor(bitsY);
            byte[] bytes = new byte[1];
            ret.CopyTo(bytes, 0);
            return bytes[0];
        }

        #endregion

        #region byte[]

        public static byte[] And(this byte[] x, byte[] y)
        {
            if (x.Length != y.Length)
                throw new ArgumentException("Both items must be the same size");

            byte[] ret = new byte[x.Length];
            ret.Length.IterateIndex(i =>
            {
                ret[i] = x[i].And(y[i]);
            });
            return ret;
        }

        public static T FromByteArray<T>(this byte[] obj)
        {
            BinaryFormatter serializer = new BinaryFormatter();
            T ret = default(T);

            using (MemoryStream s = new MemoryStream())
            {
                s.Write(obj, 0, obj.Length);
                s.Seek(0, SeekOrigin.Begin);
                ret = (T)serializer.Deserialize(s);
            }
            return ret;
        }

        // TODO: provide a better implementation of this
        //public static T FromCompressedByteArray<T>(this byte[] obj)
        //{
        //    BinaryFormatter serializer = new BinaryFormatter();
        //    T ret = default(T);

        //    using (MemoryStream s = new MemoryStream())
        //    {
        //        s.Write(obj, 0, obj.Length);
        //        s.Seek(0, SeekOrigin.Begin);
        //        GZipStream decompressor = new GZipStream(s, CompressionMode.Decompress);
        //        ret = (T)serializer.Deserialize(decompressor);
        //        decompressor.Close();
        //    }
        //    return ret;
        //}

        public static string GetMimeType(this byte[] data)
        {
            if (data.Length == 0)
                return "application/unknown";

            IntPtr mimeOut = default(IntPtr);
            int result = FindMimeFromData(IntPtr.Zero, string.Empty, data, data.Length, null, 0, out mimeOut, 0);
            if (result != 0)
                throw Marshal.GetExceptionForHR(result);

            string mime = Marshal.PtrToStringUni(mimeOut);
            Marshal.FreeCoTaskMem(mimeOut);
            return mime;
        }

        public static string GetSHA1Hash(this byte[] data)
        {
            if (data == null || data.Length == 0)
            {
                throw new ArgumentException("An empty array cannot be hashed.");
            }

            Byte[] hash = new SHA1CryptoServiceProvider().ComputeHash(data);
            return Convert.ToBase64String(hash);
        }

        public static string GetSHA256Hash(this byte[] data)
        {
            if (data == null || data.Length == 0)
            {
                throw new ArgumentException("An empty array cannot be hashed.");
            }

            Byte[] hash = new SHA256CryptoServiceProvider().ComputeHash(data);
            return Convert.ToBase64String(hash);
        }

        public static string GetSHA512Hash(this byte[] data)
        {
            if (data == null || data.Length == 0)
            {
                throw new ArgumentException("An empty array cannot be hashed.");
            }

            Byte[] hash = new SHA512CryptoServiceProvider().ComputeHash(data);
            return Convert.ToBase64String(hash);
        }

        public static byte[] Nand(this byte[] x, byte[] y)
        {
            return x.And(y.Not());
        }

        public static byte[] Nor(this byte[] x, byte[] y)
        {
            return x.Or(y.Not());
        }

        public static byte[] Not(this byte[] x)
        {
            byte[] ret = new byte[x.Length];
            ret.Length.IterateIndex(i =>
            {
                ret[i] = ret[i].Not();
            });
            return ret;
        }

        public static byte[] Or(this byte[] x, byte[] y)
        {
            if (x.Length != y.Length)
                throw new ArgumentException("Both items must be the same size");

            byte[] ret = new byte[x.Length];
            ret.Length.IterateIndex(i =>
            {
                ret[i] = x[i].Or(y[i]);
            });
            return ret;
        }

        public static string ToBase64(this byte[] obj)
        {
            return Convert.ToBase64String(obj);
        }

        public static Stream ToStream(this byte[] obj)
        {
            MemoryStream ret = new MemoryStream();
            ret.Write(obj, 0, obj.Length);
            ret.Seek(0, SeekOrigin.Begin);
            return ret;
        }

        public static byte[] Xnor(this byte[] x, byte[] y)
        {
            return x.Xor(y.Not());
        }

        public static byte[] Xor(this byte[] x, byte[] y)
        {
            if (x.Length != y.Length)
                throw new ArgumentException("Both items must be the same size");

            byte[] ret = new byte[x.Length];
            ret.Length.IterateIndex(i =>
            {
                int b1 = x[i];
                int b2 = y[i];
                ret[i] = ((byte)(b1 ^ b2));
            });
            return ret;
        }

        #endregion
    }
}
